﻿/*
 * Copyright (c) HiSilicon (Shanghai) Technologies Co., Ltd. 2023-2023. All rights reserved.
 */

#include "BurnCtrlHsan.h"
#include "../platform/3XBurn/include/Burn.h"

namespace  {
    const int OPEN_USB_TIMEOUT = 30000;
    using namespace BurnToolSpace;
}
QMap<unsigned long long, BurnCtrlHsan *> BurnCtrlHsan::myCtrlMap;
BurnCtrlHsan::BurnCtrlHsan(ChipType type, QObject *parent)
    : BurnCtrl(type, parent),
      handle(0),
      mutex(new QMutex()),
      isEmptyFlash(false)
{
    RegisterCallback(SetTextEditReceivePrint, nullptr, SetProgress);
}

void BurnCtrlHsan::InitBurnCmdByArray(const QMap<int, BIN_INFO> &infoArray)
{
    QString saveDir = QCoreApplication::applicationDirPath() + "/tempFile" + "_" + QString::number(com);
    QDir dir(saveDir);
    if (!dir.exists()) {
        dir.mkdir(saveDir);
    }
    this->infoArray.clear();
    infoArrayDlg = infoArray;

    for (auto it = infoArray.begin(); it != infoArray.end(); it++) {
        BIN_INFO info;
        QFileInfo fileInfo(it->path);
        QString strFileName;
        if (it->fileName.isEmpty()) {
            strFileName = fileInfo.fileName();
        } else {
            strFileName = it->fileName;
        }
        QString saveFile = saveDir + "/" + strFileName;

        QString cleanPath = QDir::cleanPath(it->path);
        if (cleanPath.isEmpty()) {
            continue;
        }
        QFile oldFile(cleanPath);
        QFile newFile(saveFile);
        if (!oldFile.open(QIODevice::ReadOnly)) {
            continue;
        }
        if (newFile.exists()) {
            newFile.remove();
        }
        if (!newFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
            oldFile.close();
            continue;
        }
        oldFile.seek(DataConvertor::Str2U32DecHex(it->fileIndex));
        QByteArray fileText = oldFile.read(DataConvertor::Str2U32DecHex(it->fileSize));
        int ret = newFile.write(fileText);
        oldFile.close();
        newFile.close();
        if (ret == -1) {
            continue;
        }
        info = *it;
        info.path = saveFile;
        this->infoArray[it.key()] = info;
    }
}

void BurnCtrlHsan::StartConnect()
{
    SetChipType(static_cast<long int>(GetChipType()));
    if (!InitTransport()) {
        RemoveTempDir();
        BurnFileFailed();
        return;
    }
    myCtrlMap.insert(handle, this);
    comHandleMap.insert(handle, false);
    emit SignalSetProgressRange(0, SHRT_MAX);
    QString esbcBinFilePath = "";
    QString ddrInitBinFilePath = "";
    QString astbootBinFilePath = "";
    for (auto it = infoArray.begin(); it != infoArray.end(); it++) {
        if (it->type == IMAGE_TYPE::IMAGE_TYPE_ESBC_UART || it->type == IMAGE_TYPE::IMAGE_TYPE_ESBC_FASTBOOT) {
            esbcBinFilePath = it->path;
            break;
        }
    }
    for (auto it = infoArray.begin(); it != infoArray.end(); it++) {
        if (it->type == IMAGE_TYPE::IMAGE_TYPE_ALL_FASTBOOT || it->type == IMAGE_TYPE::IMAGE_TYPE_UBOOT_ORIGIN) {
            astbootBinFilePath = it->path;
            break;
        }
    }
    for (auto it = infoArray.begin(); it != infoArray.end(); it++) {
        if (it->type == IMAGE_TYPE::IMAGE_TYPE_DDR) {
            ddrInitBinFilePath = it->path;
            break;
        }
    }
    char *cAstboot = nullptr;
    if (astbootBinFilePath.isEmpty()) {
        cAstboot = nullptr;
    } else {
        QByteArray astbootBinFilePathArray = Global::QString2Gbk(astbootBinFilePath);
        cAstboot = new char[astbootBinFilePathArray.size() + 1]();
        memset_s(cAstboot, astbootBinFilePathArray.size() + 1, 0, astbootBinFilePathArray.size() + 1);
        memcpy_s(cAstboot, astbootBinFilePathArray.size() + 1,
                 astbootBinFilePathArray.data(), astbootBinFilePathArray.size());
    }
    int retValue = 0;
    if (!isEmptyFlash) {
        retValue = DownloadSmartHomeBoot(handle, nullptr, nullptr, nullptr);
    } else {
        retValue = DownloadSmartHomeBoot(handle, cAstboot,
                                         Global::QString2Gbk(esbcBinFilePath).data(),
                                         Global::QString2Gbk(ddrInitBinFilePath).data());
    }

    if (retValue == 0) {
        BurnFileFailed();
        if (cAstboot != nullptr) {
            delete [] cAstboot;
            cAstboot = nullptr;
        }
        return;
    }

    SetIPConfig();
    int tftpRet = StartTftpServer(handle, ipAddress.toLatin1(), 69);
    if (tftpRet == 0) {
        OutputString(QString("tftp server failed\n"));
        BurnFileFailed();
        return;
    }

    bool isPortInUse = false;
    if (tftpRet == 2) { // 2
        OutputString(QString("Port: 69 is occupied\n"));
        isPortInUse = true;
    }

    if (!isEmptyFlash && way == TransportWay::SERIAL) {
        retValue = RefreshSmartHomeUboot(handle, cAstboot);
        Sleep(100); // 100
        SetIPConfig();
    }

    if (cAstboot != nullptr) {
        delete [] cAstboot;
        cAstboot = nullptr;
    }

    if (retValue == 0) {
        if (!isPortInUse) {
            CloseTftpServer(handle);
        }
        BurnFileFailed();
        return;
    }
    for (int i = 0; i < vecEraseConfig.size(); i++) {
        if ((eraseConfig & vecEraseConfig[i].id) != 0) {
            SendBytesStr("mtd erase " + vecEraseConfig[i].name + "\r\n");
            Sleep(1000); // 1000
        }
    }
    for (auto it = infoArray.begin(); it != infoArray.end(); it++) {
        if (isCancel == true) {
            if (!isPortInUse) {
                CloseTftpServer(handle);
            }
            BurnFileFailed();
            return;
        }
        if (it->type == IMAGE_TYPE::IMAGE_TYPE_ESBC_UART || it->type == IMAGE_TYPE::IMAGE_TYPE_ESBC_FASTBOOT ||
            it->type == IMAGE_TYPE::IMAGE_TYPE_ALL_FASTBOOT || it->type == IMAGE_TYPE::IMAGE_TYPE_UBOOT_ORIGIN ||
            it->type == IMAGE_TYPE::IMAGE_TYPE_DDR) {
            continue;
        }
        int fileType = GetFileType(it->type);
        bool ok = false;
        if (DownloadFileToFlashPartition(handle, Global::QString2Gbk(it->path).data(),
                                         fileType, it->burnAddr.toULong(&ok, 0x10)) == 0) {  // 区分文件类型
            if (!isPortInUse) {
                CloseTftpServer(handle);
            }
            BurnFileFailed();
            return;
        }
    }
    SendBytesStr("reset\r\n");
    if (!isPortInUse) {
        CloseTftpServer(handle);
    }

    emit SignalBurnFileFinish();
    mutex->lock();
    if (handle != 0) {
        DestroyDevice(handle);
        handle = 0;
        myCtrlMap.remove(handle);
    }
    mutex->unlock();
    isOpen = false;
    RemoveTempDir();
}

void BurnCtrlHsan::SendBytesStr(QString cmd)
{
    SendBytes(handle, reinterpret_cast<PBYTE>(cmd.toLatin1().data()), cmd.size());
    OutputString(cmd);
    Sleep(100); // 100
}

void BurnCtrlHsan::SetIPConfig()
{
    SendBytesStr("setenv serverip " + ipAddress + "\r\n");
    SendBytesStr("setenv ipaddr " + ipInfo.ip + "\r\n");
    SendBytesStr("setenv netmask " + ipInfo.subnetMask + "\r\n");
    SendBytesStr("setenv gatewayip " + ipInfo.gateway + "\r\n");
    SendBytesStr("saveenv\r\n");
}

int BurnCtrlHsan::GetFileType(IMAGE_TYPE imageType) const
{
    if (imageType == IMAGE_TYPE::IMAGE_TYPE_ESBC) {
        return static_cast<int>(FileType::ESBC);
    } else if (imageType == IMAGE_TYPE::IMAGE_TYPE_UBOOT) {
        return static_cast<int>(FileType::UBOOT);
    } else if (imageType == IMAGE_TYPE::IMAGE_TYPE_KERNEL) {
        return static_cast<int>(FileType::KERNEL);
    } else if (imageType == IMAGE_TYPE::IMAGE_TYPE_ROOTFS_RW || imageType == IMAGE_TYPE::IMAGE_TYPE_ROOTFS_RO) {
        return static_cast<int>(FileType::ROOTFS);
    } else if (imageType == IMAGE_TYPE::IMAGE_TYPE_FWK_JFFS2 || imageType == IMAGE_TYPE::IMAGE_TYPE_FWK_SQUASHFS) {
        return static_cast<int>(FileType::FWK);
    }
    return static_cast<int>(FileType::ESBC);
}

HSAN_CHIP_TYPE BurnCtrlHsan::GetChipType() const
{
    HSAN_CHIP_TYPE ret = HSAN_CHIP_TYPE::LUOFU;
    if (chipType == ChipType::HSANLUOFU) {
        ret = HSAN_CHIP_TYPE::LUOFU;
    } else if (chipType == ChipType::HSANXILING) {
        ret = HSAN_CHIP_TYPE::XILING;
    } else if (chipType == ChipType::HSANEMEI) {
        ret = HSAN_CHIP_TYPE::EMEI;
    } else if (chipType == ChipType::HSANTIANGONG0) {
        ret = HSAN_CHIP_TYPE::TG0;
    } else if (chipType == ChipType::HSANTIANGONG1) {
        ret = HSAN_CHIP_TYPE::TG1;
    } else if (chipType == ChipType::HSANTIANGONG2) {
        ret = HSAN_CHIP_TYPE::TG2;
    }
    return ret;
}

bool BurnCtrlHsan::InitTransport()
{
    if (way == TransportWay::SERIAL) {
        return InitSerialPortTransport();
    } else if (way == TransportWay::SERIAL_AND_USB) {
        OutputString(QString("Please reset the device, the waiting time is about 30 seconds.\n"));
        QString comStr = "COM" + QString::number(com);
        handle = OpenUsbAndSerialPort(deviceInfo.vid, deviceInfo.pid, "-1", comStr.toLatin1(), OPEN_USB_TIMEOUT);
        if (handle == 0) {
            OutputString(QString("error: Open Usb failed! vid = 0x%1, pid = 0x%2 .\n").
                         arg(deviceInfo.vid).arg(deviceInfo.pid));
            return false;
        }
    }
    return true;
}

bool BurnCtrlHsan::InitSerialPortTransport()
{
    OutputString(QString("Please reset the device, the waiting time is about 30 seconds.\n"));
    QString comStr = "COM" + QString::number(com);
    handle = OpenSerialPort(comStr.toLatin1(), baud, Global::Stopbits2Int(stopbits), Global::Parity2Int(parity));
    if (handle == 0) {
        OutputString(QString("error: Open serial port :%1 failed, It may be caused by the following reasons:\n").
                     arg(comStr));
        OutputString(QString("1. Serial port is occupied or does not exist.\n").arg(comStr));
        OutputString(QString("2. Check baud rate, stop bits, parity is correct.\n").arg(comStr));
        return false;
    }
    return true;
}

void BurnCtrlHsan::OutputString(QString editStr)
{
    if (!isMultipleBurn) {
        InsertPlainText(editStr);
    }
}

void BurnCtrlHsan::RemoveTempDir() const
{
    QString saveDir = QCoreApplication::applicationDirPath() + "/tempFile" + "_"
            + QString::number(com);
    QDir dir(saveDir);
    if (dir.exists()) {
        dir.removeRecursively();
    }
}

void BurnCtrlHsan::SetHSANTypeFlag(QString type)
{
    if (type == "Serial") {
        way = TransportWay::SERIAL;
    } else if (type == "USB") {
        way = TransportWay::SERIAL_AND_USB;
    }
}

void BurnCtrlHsan::SetTextEditReceivePrint(long long mHandle, const char* comNum,
                                           const char* szInfo, const char* deBugszInfo, BOOL flag)
{
    if (strlen(szInfo) == 0) {
        return;
    }
    qDebug() <<__FUNCTION__<< "Info: " << QString::fromUtf8(szInfo);
    qDebug() <<__FUNCTION__<< "Debug: " << QString::fromUtf8(deBugszInfo);
    if (myCtrlMap.find(mHandle) != myCtrlMap.end() && myCtrlMap.value(mHandle) != nullptr) {
        QString log = QString::fromUtf8(szInfo);
        myCtrlMap.value(mHandle)->OutputString(log);
    }
    Q_UNUSED(comNum);
    Q_UNUSED(flag);
}

void BurnCtrlHsan::SetProgress(long long mHandle, int progress)
{
    Q_UNUSED(mHandle);
    Q_UNUSED(progress);
}

void BurnCtrlHsan::BurnCtrlCancel()
{
    mutex->lock();
    if (handle != 0 && !comHandleMap.value(handle)) {
        Cancel(handle);
        comHandleMap.remove(handle);
    }
    mutex->unlock();
}

void BurnCtrlHsan::BurnFileFailed()
{
    mutex->lock();
    if (handle != 0) {
        DestroyDevice(handle);
        handle = 0;
        myCtrlMap.remove(handle);
    }
    mutex->unlock();
    emit SignalBurnFileFailed();
    RemoveTempDir();
    return;
}

void BurnCtrlHsan::SetIsEmptyFlash(bool isEmpty)
{
    isEmptyFlash = isEmpty;
}
